package com.machineapps.framework.repository;

import java.lang.reflect.Field;
import java.lang.reflect.ParameterizedType;
import java.lang.reflect.Type;
import java.util.ArrayList;
import java.util.List;

import android.content.ContentValues;
import android.content.Context;
import android.database.Cursor;
import android.database.SQLException;
import android.database.sqlite.SQLiteDatabase;
import android.util.Log;

import com.machineapps.botesom2.BoteSomConstants;
import com.machineapps.framework.annotation.Column;
import com.machineapps.framework.annotation.Id;
import com.machineapps.framework.annotation.Table;
import com.machineapps.framework.model.Entity;

public abstract class RepositorySQLite<T extends Entity> implements Repository<T> {

	private SQLiteDatabase database;

	/** Utility Class to open, create and update the database. */
	private SQLiteHelper dbHelper;

	public RepositorySQLite(Context context, String databaseName, int mode) {
		// Open the Database
		this.database = context.openOrCreateDatabase(databaseName, mode, null);
		this.database.close();
		this.dbHelper = new SQLiteHelper(context, databaseName, BoteSomConstants.DB_VERSION);

		// Open the database
		this.database = this.dbHelper.getWritableDatabase();
	}

	/**
	 * @return The database delete script.
	 */
	protected String getScriptDeleteDatabase() {
		return "DROP TABLE IF EXISTS " + getTable();
	}

	/**
	 * @return the database
	 */
	protected SQLiteDatabase getDatabase() {
		return this.database;
	}

	protected String getTable() {
		return getEntityNewInstance().getClass().getAnnotation(Table.class).name();
	}

	@SuppressWarnings("unchecked")
	protected String[] getColumns() {
		String[] columns = null;
		List<String> columnsList = new ArrayList<String>();
		String columnName = null;

		Class clazz = getEntityNewInstance().getClass();
		Field[] field = clazz.getDeclaredFields();

		for (int i = 0; i < field.length; i++) {
			if (field[i].getAnnotation(Column.class) != null) {
				columnName = field[i].getAnnotation(Column.class).name();
				columnsList.add(columnName);
			}
		}

		columns = new String[columnsList.size()];
		return columnsList.toArray(columns);
	}

	@SuppressWarnings("unchecked")
	private Object getEntityNewInstance() {
		Object object = null;

		try {
			ParameterizedType p = (ParameterizedType) getClass().getGenericSuperclass();
			Type type = p.getActualTypeArguments()[0];
			object = ((Class) type).newInstance();
		} catch (InstantiationException e) {
			Log.e(RepositorySQLite.class.getName(), "InstantiationException", e);
		} catch (IllegalAccessException e) {
			Log.e(RepositorySQLite.class.getName(), "IllegalAccessException", e);
		}
		return object;
	}

	/**
	 * @see com.machineapps.framework.repository.Repository#save(T)
	 */
	public T save(T entity) {
		long id = ((Entity) entity).getId();

		if (id != 0) {
			update(entity);
		} else {
			insert(entity);
		}

		return entity;
	}

	/**
	 * @see com.machineapps.framework.repository.Repository#remove(long)
	 */
	public void remove(long id) {
		String where = " _id = ? ";
		String[] whereArgs = new String[] { String.valueOf(id) };

		getDatabase().delete(getTable(), where, whereArgs);
	}

	@SuppressWarnings("unchecked")
	public T get(long id) {
		Object object = null;
		Cursor c = null;

		try {
			c = getDatabase().query(getTable(), getColumns(), " _id = " + id, null, null, null, null);

			if (c.getCount() > 0) {
				c.moveToFirst();
			
				object = getEntityNewInstance();
				Field[] field = object.getClass().getDeclaredFields();

				for (int j = 0; j < field.length; j++) {
					if (field[j].getAnnotation(Column.class) != null) {
						String columnName = field[j].getAnnotation(Column.class).name();
						int columnIndex = c.getColumnIndex(columnName);
						field[j].setAccessible(true);

						Class<?> clazzType = field[j].getType();
						
						if (clazzType.equals(Long.class) || clazzType.getName().equals("long")) {
							field[j].set(object, c.getLong(columnIndex));
						} else if (clazzType.equals(String.class)) {
							field[j].set(object, c.getString(columnIndex));
						}
					}
				}
			}
			
		} catch (SQLException e) {
			Log.e(RepositorySQLite.class.getName(), "SQLException", e);
		} catch (IllegalAccessException e) {
			Log.e(RepositorySQLite.class.getName(), "IllegalAccessException", e);
		} finally {
			if (c != null && !c.isClosed()) {
				c.close();
			}
		}

		return (T) object;
	}

	@SuppressWarnings("unchecked")
	public List<T> getAll() {
		List<T> list = new ArrayList<T>();
		Cursor c = null;

		try {
			c = getDatabase().query(getTable(), getColumns(), null, null, null, null, null);
			c.moveToFirst();

			for (int i = 0; i < c.getCount(); ++i) {
				Object object = getEntityNewInstance();
				Field[] field = object.getClass().getDeclaredFields();

				for (int j = 0; j < field.length; j++) {
					if (field[j].getAnnotation(Column.class) != null) {
						String columnName = field[j].getAnnotation(Column.class).name();
						int columnIndex = c.getColumnIndex(columnName);
						field[j].setAccessible(true);

						Class<?> clazzType = field[j].getType();
						
						if (clazzType.equals(Long.class) || clazzType.getName().equals("long")) {
							field[j].set(object, c.getLong(columnIndex));
						} else if (clazzType.equals(String.class)) {
							field[j].set(object, c.getString(columnIndex));
						}
					}
				}

				list.add((T) object);
				c.moveToNext();
			}
		} catch (SQLException e) {
			Log.e(RepositorySQLite.class.getName(), "SQLException", e);
		} catch (IllegalAccessException e) {
			Log.e(RepositorySQLite.class.getName(), "IllegalAccessException", e);
		} finally {
			if (c != null && !c.isClosed()) {
				c.close();
			}
			
			if(getDatabase() != null && getDatabase().isOpen()){
				getDatabase().close();
			}
		}

		return list;
	}

	/**
	 * Closes database.
	 */
	public void close() {
		if (getDatabase() != null) {
			getDatabase().close();
		}

		if (this.dbHelper != null) {
			this.dbHelper.close();
		}
	}

	private void insert(T entity) {
		ContentValues values = new ContentValues();
		Field[] field = entity.getClass().getDeclaredFields();

		try {
			String fieldValue = "";
			String fieldName = "";

			for (int i = 0; i < field.length; i++) {
				if (field[i].getAnnotation(Column.class) != null && field[i].getAnnotation(Id.class) == null) {
					field[i].setAccessible(true);
					fieldName = field[i].getAnnotation(Column.class).name();
					fieldValue = field[i].get(entity).toString();
					values.put(fieldName, fieldValue);
				}
			}
		} catch (IllegalArgumentException e) {
			Log.e(RepositorySQLite.class.getName(), "InstantiationException", e);
		} catch (IllegalAccessException e) {
			Log.e(RepositorySQLite.class.getName(), "InstantiationException", e);
		}

		getDatabase().insert(getTable(), "", values);
	}

	private void update(T entity) {
		ContentValues values = new ContentValues();
		Field[] field = entity.getClass().getDeclaredFields();

		try {
			String fieldValue = "";
			String fieldName = "";

			for (int i = 0; i < field.length; i++) {
				if (field[i].getAnnotation(Column.class) != null && field[i].getAnnotation(Id.class) == null) {
					field[i].setAccessible(true);
					fieldName = field[i].getAnnotation(Column.class).name();
					fieldValue = field[i].get(entity).toString();
					values.put(fieldName, fieldValue);
				}
			}
		} catch (IllegalArgumentException e) {
			Log.e(RepositorySQLite.class.getName(), "InstantiationException", e);
		} catch (IllegalAccessException e) {
			Log.e(RepositorySQLite.class.getName(), "InstantiationException", e);
		}

		String where = " _id = ?";
		String[] whereArgs = new String[] { String.valueOf(((Entity) entity).getId()) };

		getDatabase().update(getTable(), values, where, whereArgs);
	}

}
